## 2.1: Find out how to access files with and without code
## blocks. What is the benefit of the code block?

## 2.1.2 with blocks

filename = "users.txt"
File.read(filename) do |file|
  file.each_line do |line|
    puts line
  end
end

# One advantage is, that one does not have to manually call
# `close` on the file object. Similar to reading a file
# using a context manager (with open ... as ...) in Python.

# Another advantage is, that the `file`, as well as the
# `line` are bound and can be accessed

# An advantage is, that `file` and `line` are only bound
# inside the block, and not outside of it. This prevents
# misuse.

# A disadvantage is, that `read` by default reads the whole
# file into memory, which can be a problem for big
# files. This can be avoided using `foreach` of `IO` or
# `File`:

filename = "users.txt"
IO.foreach(filename) do |line|
  puts "got line: #{line}"
end

# File inherits from IO.
filename = "users.txt"
File.foreach(filename) do |line|
  puts line
end

# The following one reads all at once again:

f = File.read(filename)
f.each_line do |line|
  puts line
end

## 2.1.2 without blocks

filename = "users.txt"

file = File.open(filename, 'r')
file_data = file.read
puts "file content: #{file_data}"
file.close

file = File.open(filename, 'r')
file_data = file.readlines
puts "file content lines: #{file_data}"
file.close

file = File.open(filename, 'r')
file_data = file.readlines.map(&:chomp)
puts "file content lines no newline: #{file_data}"
file.close

fileObj = File.new(filename, 'r')
while (line = fileObj.gets)
  puts(line)
end
fileObj.close

puts File.read(filename)

## 2.2 How would you translate a hash to an array? Can you
## translate arrays to hashes?

my_hash = {:a => 1, :b => 2, :c => 3}
my_hash.to_a

## 2.3 Can you iterate through a hash?

my_hash.to_a.each do
  |entry|
  puts "entry: #{entry}"
end

## 2.4 You can use Ruby arrays as stacks. What other common
## data structures do arrays support?

# They can be used as:
# + Stack (push, pop)
# + LinkedList (head, tail)
# + Set (include?, insert, intersection, union)
# + FIFO Queue (push, last), FILO Queue (Stack)

## 2.5 Print the contents of an array of sixteen numbers,
## four numbers at a time, using just each. Now, do the same
## with each_slice in Enumerable.

arr = [0, 1, 2, 3,
       4, 5, 6, 7,
       8, 9, 10, 11,
       12, 13, 14, 15]

# I think no elegant solution is possible, because `each`
# does not give the index of the current element. Seems one
# has to mutate an outside variable.
counter = 0
row = []
arr.each do
  |elem|
  row.append elem
  if (counter % 4) == 3
    puts "#{row}"
    row = []
  end
  counter += 1
end
unless arr.empty?
  puts "#{row}"
end

# Much easier using `each_slice`:
arr.each_slice(4).to_a.each do |s| puts s end

## 2.6 The Tree class was interesting, but it did not allow
## you to specify a new tree with a clean user
## interface. Let the initializer accept a nested structure
## of hashes. You should be able to specify a tree like
## this:

## {'grandpa' => { 'dad' => {'child 1' => {}, 'child 2' =>
## {} }, 'uncle' => {'child 3' => {}, 'child 4' => {} } } }.

class Tree
  attr_accessor :children, :node_name

  def initialize (hash, name=:root)
    @children = []
    @node_name = name
    # Check, if there are more children.
    if hash.empty?
      @node_name = name
      @children = []
    else
      # If there are more children, then create a tree for
      # each child and add them all to children.
      hash.to_a.each do
        |(name, value)|  # block destructuring
        child = Tree.new(value, name)
        @children.append child
      end
    end
  end
end

family = {
  'grandpa' => {
    'dad' => {
      'child 1' => {},
      'child 2' => {}
    },
    'uncle' => {
      'child 3' => {},
      'child 4' => {}
    }
  }
}
tree = Tree.new(family)

## 2.7 Write a simple grep that will print the lines of a
## file having any occurrences of a phrase anywhere in that
## line. You will need to do a simple regular expression
## match and read lines from a file. (This is surprisingly
## simple in Ruby.) If you want, include line numbers.

def simple_grep filename, search_string
  File.read(filename).split("\n").each_with_index do
    |line, index|
    if line.include? search_string
      puts "#{index}: #{line}"
    end
  end
end

simple_grep "user.txt", "a"
